﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

using Roslyn.Compilers.CSharp;
using Roslyn.Compilers.Common;

using King.Reflection;
using King.Extensions;
using System.IO;

namespace King.CSharp.Parser {

    public sealed class SyntaxInfoObjectAttribute : Attribute {

        private Type[] m_syntaxTypes;

        public SyntaxInfoObjectAttribute(params Type[] syntaxTypes) {
            m_syntaxTypes = syntaxTypes;
        }

        public Type[] SyntaxTypes() {
            return m_syntaxTypes;
        }
    }

    public sealed class SyntaxInfoTree {

        static SyntaxInfoTree() {

            m_syntaxTypeToRoslynType =
                // for every non-abstract RoslynObject in the assembly
               (from o in typeof(SyntaxInfoTree).Assembly.GetTypes()
                where !o.IsAbstract
                where typeof(SyntaxInfoObject).IsAssignableFrom(o)

                // that has a RoslynObjectAttribute
                let attribute = o.GetAttribute<SyntaxInfoObjectAttribute>()
                where attribute != null

                // for every SyntaxType associated with the RoslynObject
                from s in attribute.SyntaxTypes()
                select new {
                    SyntaxType = s,
                    InfoType = o

                    // associate the SyntaxTypes with the RoslynObject
                }).ToDictionary(
                    o => o.SyntaxType,
                    o => o.InfoType
                );
        }

        private static readonly Dictionary<Type, Type> m_syntaxTypeToRoslynType;

        private ISemanticModel m_semanticModel;
        private CancellationToken m_cancellationToken;
        private Dictionary<CommonSyntaxNode, SyntaxInfoObject> m_cache;

        public SyntaxInfoTree(ISemanticModel semanticModel, CancellationToken cancellationToken) {
            m_semanticModel = semanticModel;
            m_cancellationToken = cancellationToken;
            m_cache = new Dictionary<CommonSyntaxNode, SyntaxInfoObject>();
        }

        public SyntaxInfoObject Get(CommonSyntaxNode node) {
            var result = m_cache.GetValueOrDefault(node);
            if (result == null) {
                var type = m_syntaxTypeToRoslynType.GetValueOrDefault(node.GetType());
                if (type == null)
                    return null;
                result = m_cache[node] = (SyntaxInfoObject)type.Activate(this, node);
            }
            return result;
        }
        public T TryGet<T>(CommonSyntaxNode node) {
            var type = m_syntaxTypeToRoslynType[node.GetType()];
            if (!typeof(T).IsAssignableFrom(type))
                return default(T);
            return (T)(object)Get(node);
        }
        public T Get<T>(CommonSyntaxNode node) {
            return (T)(object)Get(node);
        }

        public ISemanticInfo GetSemanticInfo(CommonSyntaxNode node) {
            return m_semanticModel.GetSemanticInfo(node, m_cancellationToken);
        }
        public ISymbol GetSymbol(CommonSyntaxNode node) {
            return GetSemanticInfo(node).Symbol;
        }
        public ITypeSymbol GetType(CommonSyntaxNode node) {
            return GetSemanticInfo(node).Type;
        }

        public ISemanticModel SemanticModel {
            get { return m_semanticModel; }
        }
        public CommonSyntaxTree SyntaxTree {
            get { return m_semanticModel.SyntaxTree; }
        }
        public CancellationToken CancellationToken {
            get { return m_cancellationToken; }
        }

        public NamespaceSyntaxInfo GlobalNamespace {
            get { return (NamespaceSyntaxInfo)Get(m_semanticModel.SyntaxTree.Root); }
        }
        public IEnumerable<TypeSyntaxInfo> Types() {
            return GlobalNamespace.Types(recursive: true);
        }
    }
    public abstract class SyntaxInfoObject {

        private CommonSyntaxNode m_node;
        private SyntaxInfoTree m_loader;

        internal SyntaxInfoObject(SyntaxInfoTree loader, CommonSyntaxNode node) {
            m_loader = loader;
            m_node = node;
        }

        internal SyntaxInfoTree Loader {
            get { return m_loader; }
        }
        internal CommonSyntaxNode CommonSyntaxNode {
            get { return m_node; }
        }
    }

    [SyntaxInfoObject(
        typeof(CompilationUnitSyntax),
        typeof(NamespaceDeclarationSyntax)
    )]
    public class NamespaceSyntaxInfo : SyntaxInfoObject {

        internal NamespaceSyntaxInfo(SyntaxInfoTree loader, CommonSyntaxNode namespaceOrCompilationUnit)
            : base(loader, namespaceOrCompilationUnit) {
        }

        internal IEnumerable<TypeSyntaxInfo> Types(bool recursive) {

            // gather all members of namespace or compilation unit
            dynamic dNamespaceOrCompilationUnit = CommonSyntaxNode;
            var dMembers = dNamespaceOrCompilationUnit.Members;

            // foreach member
            foreach (var dMember in dMembers) {
                dynamic dTypeOrNamespace = Loader.Get(dMember);

                // yield it if it's a type
                if (dTypeOrNamespace is TypeSyntaxInfo)
                    yield return (TypeSyntaxInfo)dTypeOrNamespace;

                // recurse
                if (recursive) {
                    foreach (TypeSyntaxInfo enclosedType in dTypeOrNamespace.Types(recursive))
                        yield return enclosedType;
                }
            }
        }

        public IEnumerable<TypeSyntaxInfo> Types() {
            return Types(false);
        }
    }

    public abstract class MemberSyntaxInfo : SyntaxInfoObject {

        private static IEnumerable<CommonSyntaxNode> NextOrPreviousMembers(CommonSyntaxNode member, bool next = true) {
            var delta = next ? 1 : -1;

            // get next (or previous) member declared in the parent type
            dynamic dMember = member;
            var dType = dMember.Parent;
            var dMembers = dType.Members;
            var dIndex = dMembers.IndexOf(dMember);
            var dNextIndex = dIndex + delta;

            while (dNextIndex >= 0 && dNextIndex < dMembers.Count) {
                yield return dMembers[dNextIndex];
                dNextIndex += delta;
            }
        }

        internal MemberSyntaxInfo(SyntaxInfoTree loader, CommonSyntaxNode member)
            : base(loader, member) {
        }

        public TypeSyntaxInfo DeclaringType {
            get {
                dynamic dMember = CommonSyntaxNode;
                var dParent = dMember.Parent;
                return Loader.Get(dParent) as TypeSyntaxInfo;
            }
        }
        public NamespaceSyntaxInfo Namespace {
            get {
                var declaringType = DeclaringType;
                if (declaringType != null)
                    return declaringType.Namespace;
                return Loader.Get<NamespaceSyntaxInfo>(CommonSyntaxNode.Parent);
            }
        }

        public IEnumerable<AttributeSyntaxInfo> Attributes(ITypeSymbol type = null) {

            dynamic dMember = CommonSyntaxNode;
            var dAttributeGroups = dMember.Attributes;

            foreach (var dAttributeGroup in dAttributeGroups) {
                foreach (var dAttribute in dAttributeGroup.Attributes) {

                    if (type != null && Loader.GetType(dAttribute) != type)
                        continue;

                    yield return Loader.Get<AttributeSyntaxInfo>(dAttribute);
                }
            }
        }
        public IEnumerable<MemberSyntaxInfo> NextMembers(bool next = true) {
            if (next == false)
                return PreviousMembers();
            return NextOrPreviousMembers(CommonSyntaxNode, next: true)
                .Select(o => Loader.TryGet<MemberSyntaxInfo>(o))
                .WhereNotDefault();
        }
        public IEnumerable<MemberSyntaxInfo> PreviousMembers() {
            return NextOrPreviousMembers(CommonSyntaxNode, next: false)
                .Select(o => Loader.TryGet<MemberSyntaxInfo>(o))
                .WhereNotDefault();
        }
    }

    [SyntaxInfoObject(
        typeof(ClassDeclarationSyntax),
        typeof(InterfaceDeclarationSyntax),
        typeof(StructDeclarationSyntax)
    )]
    public sealed class TypeSyntaxInfo : MemberSyntaxInfo {

        internal TypeSyntaxInfo(SyntaxInfoTree loader, CommonSyntaxNode type)
            : base(loader, type) {
        }

        internal IEnumerable<TypeSyntaxInfo> Types(bool recursive) {

            // gather all members of the class, interface, or struct
            dynamic dClassInterfaceOrStruct = CommonSyntaxNode;
            var dMembers = dClassInterfaceOrStruct.Members;

            // foreach member
            foreach (var dMember in dMembers) {

                // yield the nestedType
                var type = Loader.TryGet<TypeSyntaxInfo>(dMember);
                if (type == null)
                    continue;

                yield return type;

                // recurse
                if (recursive) {
                    foreach (var nestedType in type.Types(recursive))
                        yield return nestedType;
                }
            }
        }

        public bool IsClass {
            get { return CommonSyntaxNode is ClassDeclarationSyntax; }
        }
        public bool IsInterface {
            get { return CommonSyntaxNode is InterfaceDeclarationSyntax; }
        }
        public bool IsStruct {
            get { return CommonSyntaxNode is StructDeclarationSyntax; }
        }

        public IEnumerable<TypeSyntaxInfo> Types() {
            return Types(true);
        }
        public IEnumerable<MemberSyntaxInfo> Members() {
            dynamic dType = CommonSyntaxNode;
            var dMembers = dType.Members;
            foreach (var dMember in dMembers)
                yield return Loader.Get<MemberSyntaxInfo>(dMember);
        }
    }

    [SyntaxInfoObject(
        typeof(MethodDeclarationSyntax),
        typeof(ConstructorDeclarationSyntax)
    )]
    public sealed class MethodSyntaxInfo : MemberSyntaxInfo {

        internal MethodSyntaxInfo(SyntaxInfoTree loader, CommonSyntaxNode method)
            : base(loader, method) {
        }

        public bool IsConstructor {
            get { return CommonSyntaxNode is ConstructorDeclarationSyntax; }
        }
    }

    [SyntaxInfoObject(typeof(PropertyDeclarationSyntax))]
    public sealed class PropertySyntaxInfo : MemberSyntaxInfo {

        internal PropertySyntaxInfo(SyntaxInfoTree loader, CommonSyntaxNode property)
            : base(loader, property) {
        }
    }

    [SyntaxInfoObject(typeof(EventDeclarationSyntax))]
    public sealed class EventSyntaxInfo : MemberSyntaxInfo {

        internal EventSyntaxInfo(SyntaxInfoTree loader, CommonSyntaxNode @event)
            : base(loader, @event) {
        }
    }

    [SyntaxInfoObject(typeof(FieldDeclarationSyntax))]
    public sealed class FieldSyntaxInfo : MemberSyntaxInfo {

        internal FieldSyntaxInfo(SyntaxInfoTree loader, CommonSyntaxNode field)
            : base(loader, field) {
        }
    }

    [SyntaxInfoObject(typeof(AttributeSyntax))]
    public sealed class AttributeSyntaxInfo : SyntaxInfoObject {

        private static CommonSyntaxNode NextOrPreviousAttribute(CommonSyntaxNode attribute, bool next = true) {
            var delta = next ? 1 : -1;

            dynamic dAttribute = attribute;

            // get next (or previous) attribute in the current group
            var dAttributeGroup = dAttribute.Parent;
            var dAttributes = dAttributeGroup.Attributes;
            var dIndex = dAttributes.IndexOf(dAttribute);
            var dNextIndex = dIndex + delta;

            if (dNextIndex >= 0 && dNextIndex < dAttributes.Count)
                return dAttributes[dNextIndex];

            // get first attribute in next group (or last attribute in previous group)
            var dMember = dAttributeGroup.Parent;
            var dAttributeGroups = dMember.Attributes;
            var dGroupIndex = dAttributeGroups.IndexOf(dAttributeGroup);
            var dNextGroupIndex = dGroupIndex + delta;

            if (dNextGroupIndex >= 0 && dNextGroupIndex < dAttributeGroups.Count) {
                var dNextAttributes = dAttributeGroups[dNextGroupIndex].Attributes;
                return next ? dNextAttributes.First() : dNextAttributes.Last();
            }

            return null;
        }
        private static IEnumerable<CommonSyntaxNode> NextOrPreviousAttributes(CommonSyntaxNode attribute, bool next = true) {
            while (true) {
                attribute = NextOrPreviousAttribute(attribute, next);
                if (attribute == null)
                    break;
                yield return attribute;
            }
        }

        internal AttributeSyntaxInfo(SyntaxInfoTree loader, CommonSyntaxNode attribute)
            : base(loader, attribute) {
        }

        private CommonSyntaxNode NameSyntax {
            get {
                dynamic attribute = CommonSyntaxNode;
                return (CommonSyntaxNode)attribute.Name;
            }
        }

        public ISymbol Symbol {
            get { return Loader.GetSymbol(NameSyntax); }
        }
        public ITypeSymbol Type {
            get { return Loader.GetType(NameSyntax); }
        }

        public MemberSyntaxInfo Member {
            get {
                dynamic node = CommonSyntaxNode;
                var dAttributeGroup = node.Parent;
                var dMember = dAttributeGroup.Parent;
                return Loader.Get<MemberSyntaxInfo>(dMember);
            }
        }

        public IEnumerable<AttributeSyntaxInfo> NextAttributes(bool next = true) {
            if (next == false)
                return PreviousAttributes();
            return NextOrPreviousAttributes(CommonSyntaxNode, next: true)
                .Select(o => Loader.Get<AttributeSyntaxInfo>(o));
        }
        public IEnumerable<AttributeSyntaxInfo> PreviousAttributes() {
            return NextOrPreviousAttributes(CommonSyntaxNode, next: false)
                .Select(o => Loader.Get<AttributeSyntaxInfo>(o));
        }
        public IEnumerable<ArgumentSyntaxInfo> Arguments() {
            dynamic dAttribute = CommonSyntaxNode;
            var dArgumentList = dAttribute.ArgumentListOpt;
            if (dArgumentList == null)
                yield break;

            var dArguments = dArgumentList.Arguments;
            foreach (var dArgument in dArguments)
                yield return Loader.Get<ArgumentSyntaxInfo>(dArgument);
        }
    }

    [SyntaxInfoObject(
        typeof(AttributeArgumentSyntax),
        typeof(ArgumentSyntax)
    )]
    public sealed class ArgumentSyntaxInfo : SyntaxInfoObject {

        public ArgumentSyntaxInfo(SyntaxInfoTree loader, CommonSyntaxNode argument)
            : base(loader, argument) {

            var symbol = loader.GetSemanticInfo(argument);
        }

        public object Value {

            get {
                var scriptEngine = new Roslyn.Scripting.CSharp.ScriptEngine();

                // only support integer literal expressions
                dynamic dArgument = CommonSyntaxNode;
                var dExpression = dArgument.Expression;
                if (dExpression == null)
                    return null;

                var expression = dExpression.ToString();
                try {
                    var result = new Roslyn.Scripting.CSharp.ScriptEngine().Execute(expression);
                    return result;
                } catch { return null; }
            }
        }
    }
}
